package com.xxmw.transaction.common.utils.bourse;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xxmw.transaction.common.excep.service.TransactionException;
import com.xxmw.transaction.common.utils.HmacUtil;
import com.xxmw.transaction.common.utils.http.HttpClientUtil;
import com.xxmw.transaction.common.utils.http.HttpHeader;
import com.xxmw.transaction.common.utils.http.RestTemplateUtil;
import com.xxmw.transaction.model.app.BourseApi;
import com.xxmw.transaction.model.app.BourseOrder;
import com.xxmw.transaction.vo.BalanceVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 币安
 * https://binance-docs.github.io/apidocs/spot/cn/#185368440e
 * @author luowenliang
 * @since 2020/2/6 09:13
 */
@Slf4j
@Component
public class BinanceUtil {

    //币安查询近期交易列表
    private static final String TRADE_URL = "https://api.binance.com/api/v3/trades?symbol=";

    //币安下单接口
    private static final String ORDER_URL = "https://api.binance.com/api/v3/order";

    /**
     * 币安查询订单信息接口
     */
    private static final String ORDER_INFO_URL = "https://api.binance.com/api/v3/order";

    /**
     * 获取账户余额信息
     */
    private static final String ACCOUNT_INFO_URL = "https://api.binance.com/sapi/v1/capital/config/getall";

    private static final String WEB_SOCKET_URL = "wss://stream.binance.com:9443/stream";

    /**
     * 订单类型-市价单
     * 下单时强制参数： quantity or quoteOrderQty
     */
    private static final String ORDER_MARKEY_TYPE = "MARKET";

    /**
     * 订单类型-限价单
     * 下单时强制参数：timeInForce, quantity, price
     * timeInForce: GTC 成交为止 IOC 无法立即成交的部分就撤销 FOK 无法全部立即成交就撤销
     *
     */
    private static final String ORDER_LIMIT_TYPE = "LIMIT";

    /**
     * 卖出
     */
    private static final String SELL_TYPE = "SELL";

    /**
     * 买入
     */
    private static final String BUY_TYPE = "BUY";

    public void test () throws URISyntaxException, URISyntaxException {
        BourseApi api = new BourseApi();
        api.setAccessKey("iknWOeERMuQm1RxRdQgCDdXzF8he4t8iZlYy25vyyJw9YNd2LDtTSUqi4fbJJs0D");
        api.setSecretKey("K9AEjJC00eiBS1PVAPfMMnvI8Sbr0UpORs4YsGvdmF1XKxSp4mHbVdJyBe35hf9U");
//        accountInfo(api);
    }

    /**
     * 币安费率获取
     * @param symbol BTCUSDT
     * @return bitforexPrice 比费率
     */
    public static BigDecimal binancePrice (String symbol) throws Exception {
        String res = RestTemplateUtil.getForEntity(TRADE_URL + symbol + "&limit=1").getBody();
        // 测试用币安数据返回 -》
        // String res = "[{\"id\":240565825,\"price\":\"9815.89000000\",\"qty\":\"0.03891700\",\"quoteQty\":\"382.00499113\",\"time\":1581040978149,\"isBuyerMaker\":true,\"isBestMatch\":true}]";
        // log.error("binancePrice请求响应：{}", res);
        JSONArray jsonArray = JSON.parseArray(res);
        if (jsonArray != null) {
            JSONObject object = jsonArray.getJSONObject(0);
            BigDecimal bitforexPrice = new BigDecimal(object.getString("price")).setScale(5, BigDecimal.ROUND_HALF_EVEN);
            log.info("币安汇率：{} = {}", symbol, bitforexPrice);
            return bitforexPrice;
        }
        throw new Exception("获取币安交易对" + symbol + "费率失败");
    }

    /**
     * 币安下单接口
     * @param api 交易所账户信息
     * @param order 订单信息
     * @return
     */
    public static String order (BourseApi api, BourseOrder order) {
        StringBuffer body = getOrderBody(order);
        String sign = sign(api, body.toString());
        HttpHeader headers = new HttpHeader();
        headers.add("X-MBX-APIKEY", api.getAccessKey());
        log.info("币安下单参数为：{}", sign);
        String res = HttpClientUtil.post(ORDER_URL, headers, sign);
        log.info("币安下单的结果为：{}",res);
        JSONObject jsonObject = JSONObject.parseObject(res);
        if (jsonObject != null) {
            if (jsonObject.getString("code") != null && jsonObject.getString("code")!= "0") {
                log.error("币安订单" + order.getId() + "下单失败");
                order.setStatus(BourseOrder.Status.PUBLISH_ERROR.value());
                order.setRemark(jsonObject.getString("msg"));
            } else {
                order.setOutId(jsonObject.getString("orderId"));
                order.setStatus(BourseOrder.Status.PUBLISHED.value());
            }
            return res;
        }
        log.error("币安订单" + order.getId() + "下单失败");
        return null;
    }

    public static <T extends BourseOrder> void order (BourseApi api, List<T> orderList) {

    }

    /**
     * 取消订单
     * @return
     */
    public static String cancelOrder (BourseApi api, String symbol, String orderId) {
        //TODO 币安取消订单接口
        StringBuffer body = new StringBuffer().append("symbol=").append(symbol).append("&origClientOrderId=").append(orderId)
                .append("&timestamp=").append(System.currentTimeMillis());
        String sign = sign(api, body.toString());
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-MBX-APIKEY", api.getAccessKey());
        HttpEntity<String> entity = new HttpEntity<>(sign, headers);
        String res = RestTemplateUtil.deleteForObject(ORDER_URL, entity, String.class);
        log.info("币安取消订单接口返回数据:{}",res);
        return null;
    }

    /**
     * 获取用户usdt详情
     * @return
     */
    public static BalanceVo getBalance (BourseApi api, String currency) {
        JSONArray array = balanceArray(api);
        BalanceVo balanceVo = new BalanceVo();
        if (array != null) {
            for (int i = 0 ; i < array.size(); i++) {
                JSONObject data = array.getJSONObject(i);
                if (data.getString("coin").toLowerCase().equals(currency)) {
                    log.info("币安查询USDT余额为:"+data.getBigDecimal("free"));
                    balanceVo.setAvailable(data.getBigDecimal("free"));
                    balanceVo.setFrozen(data.getBigDecimal("freeze"));

                }
            }
            balanceVo.setTotal(balanceVo.getAvailable().add(balanceVo.getFrozen()));
        }
        return balanceVo;
    }

    public static Map<String, BalanceVo> getBalance (BourseApi api, List<String> currency) {
        Map<String, BalanceVo> map = new HashMap<>(16);
        if (currency.size() <= 0) {
            return map;
        }
        for (String s : currency) {
            map.put(s, getBalance(api, s));
        }
        return map;
    }

    public static JSONArray balanceArray (BourseApi api) {
        //获取用户详情
        StringBuffer body = new StringBuffer().append("recvWindow=10000&").append("timestamp=").append(System.currentTimeMillis());
        String sign = sign(api, body.toString());
        HttpHeader headers = new HttpHeader();
        headers.add("X-MBX-APIKEY", api.getAccessKey());
        // log.info("发送请求:{}",ACCOUNT_INFO_URL+"?"+sign);
        String res = HttpClientUtil.get(ACCOUNT_INFO_URL+"?"+sign, headers);
        // log.info("获取币安账号信息:{}", res);
        JSONArray array = JSONArray.parseArray(res);
        if (array != null) {
            return array;
        }
        throw new TransactionException("币安获取账户余额异常");
    }


    /**
     * 通过订单id来获取订单详情
     * @param api 账号密码实体
     * @param  order 订单实体类
     * @return
     */
    public static void orderInfo (BourseApi api,  BourseOrder order){
        long time = System.currentTimeMillis();
        Map<String, String> paramMap = new TreeMap<>();
        paramMap.put("symbol",order.getSymbol().toUpperCase());
        paramMap.put("origClientOrderId",order.getId().toString());
        paramMap.put("recvWindow","10000");
        paramMap.put("timestamp",String.valueOf(time));
        HttpHeader headers = new HttpHeader();
        headers.add("X-MBX-APIKEY", api.getAccessKey());
        StringBuilder paramSb = new StringBuilder();
        paramMap.entrySet().forEach(entry -> paramSb.append(entry.toString()).append("&"));
        String paramStr = paramSb.substring(0, paramSb.length() - 1);
        String sign = sign(api, paramStr);
        String res = HttpClientUtil.get(ORDER_INFO_URL+"?"+sign,headers);
        log.info("币安查询订单的结果为：{}",res);
        JSONObject jsonObject = JSONObject.parseObject(res);
        if (jsonObject != null && jsonObject.getString("status") != null) {
            //更新订单状态
            order.setStatus(getOrderStatus(jsonObject.getString("status")));
            order.setDealNum(jsonObject.getBigDecimal("executedQty"));
            if (jsonObject.getBigDecimal("cummulativeQuoteQty") != null && jsonObject.getBigDecimal("executedQty") != null) {
                order.setAvgPrice(jsonObject.getBigDecimal("cummulativeQuoteQty").divide(jsonObject.getBigDecimal("executedQty")));
            }
            return;
        }
        log.error("币安查询订单" + order.getId() + "失败");
    }

    /**
     * 数据签名
     * @param data
     * @return
     */
    private static String sign (BourseApi api, String data) {
        String sign = HmacUtil.binanceSign(data, api.getSecretKey());
        data = data + "&signature="+sign;
        return data;
    }

    /**
     * 根据订单类型获取请求BODY
     * @param order  下单
     * @return
     */
    private static StringBuffer getOrderBody(BourseOrder order) {
        StringBuffer body = new StringBuffer();
        long time = System.currentTimeMillis();
        Integer tradeType = order.getTradeType();
        switch (tradeType) {
            case 1:
                body.append("symbol=").append(order.getSymbol().toUpperCase()).append("&side=")
                        .append(BUY_TYPE).append("&type=").append(ORDER_MARKEY_TYPE).append("&quantity=")
                        .append(order.getNum()).append("&newClientOrderId=").append(order.getId()).append("&recvWindow=10000").append("&timestamp=").append(time);
                break;
            case 2:
                body.append("symbol=").append(order.getSymbol().toUpperCase()).append("&side=")
                        .append(SELL_TYPE).append("&type=").append(ORDER_MARKEY_TYPE).append("&quantity=")
                        .append(order.getNum()).append("&newClientOrderId=").append(order.getId()).append("&recvWindow=10000").append("&timestamp=").append(time);
                break;
            case 3:
                body.append("symbol=").append(order.getSymbol().toUpperCase()).append("&side=")
                        .append(BUY_TYPE).append("&type=").append(ORDER_LIMIT_TYPE).append("&quantity=")
                        .append(order.getNum()).append("&timeInForce=").append("GTC").append("&price=").append(order.getPrice())
                        .append("&newClientOrderId=").append(order.getId()).append("&recvWindow=10000").append("&timestamp=").append(time);
                break;
            case 4:
                body.append("symbol=").append(order.getSymbol().toUpperCase()).append("&side=")
                        .append(SELL_TYPE).append("&type=").append(ORDER_LIMIT_TYPE).append("&quantity=")
                        .append(order.getNum()).append("&timeInForce=").append("GTC").append("&price=").append(order.getPrice())
                        .append("&newClientOrderId=").append(order.getId()).append("&recvWindow=10000").append("&timestamp=").append(time);
                break;
        }
        return body;
    }

    /**
     * 将币安的状态转换成我们自己的状态码
     * @param orderStatus
     * @return
     */
    private static Integer getOrderStatus(String orderStatus) {
        Integer status = null;
        switch (orderStatus) {
            case "NEW":
                //新建订单
                status = BourseOrder.Status.PUBLISHED.value();
                break;
            case "PARTIALLY_FILLED":
                //部分成交
                status = BourseOrder.Status.DEAL_PART.value();
                break;
            case "FILLED":
                //全部成交
                status = BourseOrder.Status.DEALED.value();
                break;
            case "CANCELED":
                //已撤销
                status = BourseOrder.Status.CANCLED.value();
                break;
            case "PENDING_CANCEL":
                //撤销中(暂未使用)
                status = BourseOrder.Status.CANCELING.value();
                break;
            case "REJECTED":
                //订单被拒绝
                status = BourseOrder.Status.PUBLISH_ERROR.value();
                break;
            case "EXPIRED":
                //订单过期,不做处理
                log.info("订单过期");
                break;
            default:
                log.error("订单状态异常:{}", orderStatus);
        }
        return status;
    }


    public static void main(String[] args) throws Exception {
        //System.out.println(binancePrice("BTCUSDT"));
        /*log.info(""+System.currentTimeMillis());
        URI url = new URI(WEB_SOCKET_URL);
        BinanceWebSocketClient client = new BinanceWebSocketClient(url, new Draft_6455(), null, 5000);
        client.connect();*/

    }
}
